using System;
using System.Collections;
using System.Text.RegularExpressions;


namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Message text and format with values that can be formatted using any culture.
    /// </summary>
    /// <remarks>Typically used to pass messages that contain real numbers and dates from business
    /// logic layer to UI layer. UI layer decides how the values are to be formatted, while
    /// business layer creates the content of message.</remarks>
    [Serializable]
    public sealed class FormattedMessage
    {
        #region Members.

        IMessageFormatter formatter;
        string messageFormat;
        ArrayList values = new ArrayList();

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the <b>FormattedMessage</b> class.
        /// </summary>
        /// <param name="messageFormat">Message text and format. The message format must obey the rules of the 
        /// <see cref="System.String.Format(string, object)"/> method.</param>
        public FormattedMessage(string messageFormat)
        {
            this.messageFormat = (messageFormat != null) ? messageFormat : "";
            this.formatter = new MessageFormatter();
        }

        /// <summary>
        /// Initializes a new instance of the <b>FormattedMessage</b> class.
        /// </summary>
        /// <param name="messageFormat">Message text and format. The message format must obey the rules of the 
        /// <see cref="System.String.Format(string, object)"/> method.</param>
        /// <param name="values">Values which are to be inserted into <b>MessageFormat</b> and formatted by <b>Formatter</b>.</param>
        public FormattedMessage(string messageFormat, params object[] values)
        {
            this.messageFormat = (messageFormat != null) ? messageFormat : "";
            this.formatter = new MessageFormatter();
            if (values != null)
            {
                foreach (object val in values)
                    this.values.Add(val);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <b>FormattedMessage</b> class.
        /// </summary>
        /// <param name="messageFormat">Message text and format. The message format must obey the rules of the 
        /// <see cref="System.String.Format(string, object)"/> method.</param>
        /// <param name="formatter">An <b>IMessageFormatter</b> object that formattes the values contained in the message.</param>
        /// <param name="values">Values which are to be inserted into <b>MessageFormat</b> and formatted by <b>Formatter</b>.</param>
        public FormattedMessage(string messageFormat, IMessageFormatter formatter, params object[] values)
        {
            this.messageFormat = (messageFormat != null) ? messageFormat : "";
            this.formatter = (formatter != null) ? formatter : new MessageFormatter();
            if (values != null)
            {
                foreach (object val in values)
                    this.values.Add(val);
            }
        }

        #endregion

        #region ToString.

        /// <summary>
        /// Converts an instance of <b>FormattedMessage</b> to a <b>System.String</b>.
        /// </summary>
        /// <returns>String with formatted values.</returns>
        public override string ToString()
        {
            return ToString(this.formatter);
        }

        /// <summary>
        /// Converts an instance of <b>FormattedMessage</b> to a <b>System.String</b>.
        /// </summary>
        /// <param name="formatter">The <b>IMessageFormatter</b> that formattes the values contained in the message.</param>
        /// <returns>String with formatted values.</returns>
        public string ToString(IMessageFormatter formatter)
        {
            string[] formattedValues = new string[this.values.Count];
            for (int i = 0; i < formattedValues.Length; i++)
                formattedValues[i] = formatter.Format(this.values[i]);

            return string.Format(this.messageFormat, formattedValues);
        }

        #endregion

        #region Append.

        /// <summary>
        /// Appends the given message to the end of this instance.
        /// </summary>
        /// <param name="message">Message with values to be appended.</param>
        public void Append(FormattedMessage message)
        {
            if (message == null)
                return;

            FormattedMessage newMessage = this + message;
            this.messageFormat = newMessage.messageFormat;
            this.formatter = newMessage.formatter;
            this.values = newMessage.values;
        }

        /// <summary>
        /// Appends the given message to the end of this instance.
        /// </summary>
        /// <param name="message">String to be appended.</param>
        public void Append(string message)
        {
            if (message == null)
                return;

            this.messageFormat += message;
        }

        /// <summary>
        /// Appends the given message to the end of this instance.
        /// </summary>
        /// <param name="messageFormat">Message text and format. The message format must obey the rules of the <see cref="System.String.Format(string, object)"/> method.</param>
        /// <param name="values">Array of values which are to be inserted into message format.</param>
        public void Append(string messageFormat, params object[] values)
        {
            if (messageFormat == null)
                return;

            Append(new FormattedMessage(messageFormat, values));
        }

        #endregion

        #region Concatenation operator.

        /// <summary>
        /// Concatenates two messages into a new one that contains all the values of the original messages.
        /// </summary>
        /// <param name="lhs">The first message.</param>
        /// <param name="rhs">The second message.</param>
        /// <returns>Concatenated message.</returns>
        public static FormattedMessage operator +(FormattedMessage lhs, FormattedMessage rhs)
        {
            FormattedMessage newMessage = null;
            if (lhs == null && rhs == null)
            {
                // null + null = null
                newMessage = null;
            }
            else
            {
                if (lhs == null)
                {
                    // null + rhs = rhs
                    newMessage = rhs;
                }
                else if (rhs == null)
                {
                    // lhs + null = lhs
                    newMessage = lhs;
                }
                else
                {
                    // lhs + rhs
                    // lhs formatter will be used.

                    // All indices int rhs message format are increased. 
                    // Otherwise this would occur:  "Left {0}. Right{0}.
                    // After indices are increased: "Left {0}. Right{1}.
                    string rhsFormat = rhs.messageFormat;
                    if (lhs.values.Count > 0 && rhs.values.Count > 0)
                        rhsFormat = IncreaseParameterIndices(rhsFormat, lhs.values.Count);

                    string newFormat = lhs.messageFormat + rhsFormat;
                    newMessage = new FormattedMessage(newFormat);
                    newMessage.formatter = lhs.formatter;
                    foreach (object val in lhs.values)
                        newMessage.values.Add(val);
                    foreach (object val in rhs.values)
                        newMessage.values.Add(val);
                }
            }

            return newMessage;
        }

        private static string IncreaseParameterIndices(string msgFormat, int increaseBy)
        {
            int[] ascendingIndices = GetNumbersInCurlyBraces(msgFormat);
            Array.Sort(ascendingIndices);

            // Increase indices. Greatest ones first. Otherwise overlapping will occure.
            string newFormat = msgFormat;
            for (int i = ascendingIndices.Length - 1; i >= 0; i--)
            {
                int oldIdx = ascendingIndices[i];
                int newIdx = oldIdx + increaseBy;
                newFormat = newFormat.Replace("{" + oldIdx.ToString() + "}", "{" + newIdx + "}");
            }
            return newFormat;
        }

        private static int[] GetNumbersInCurlyBraces(string msgFormat)
        {
            Regex digitsInBraces = new Regex(@"\{\d{1,}\}");
            MatchCollection matches = digitsInBraces.Matches(msgFormat);
            int[] numbers = new int[matches.Count];

            for (int i = 0; i < numbers.Length; i++)
            {
                string sNumber = matches[i].Value.Substring(1, matches[i].Value.Length - 2);
                numbers[i] = int.Parse(sNumber);
            }

            return numbers;
        }

        #endregion

        #region Cast operators.

        /// <summary>
        /// Casts an instance of <b>FormattedMessage</b> to a <b>System.String</b>.
        /// </summary>
        /// <param name="message">Message to be converted to <b>System.String</b>.</param>
        /// <returns>String with formatted values.</returns>
        /// <remarks>Implicit cast operators allow the <b>FormattedMessage</b> to be used anywhere where strings are used.</remarks>
        public static implicit operator string(FormattedMessage message)
        {
            return message.ToString();
        }

        /// <summary>
        /// Casts a <b>System.String</b> to an instance of <b>FormattedMessage</b>.
        /// </summary>
        /// <param name="str">String.</param>
        /// <returns>FormattedMessage.</returns>
        /// <remarks>Implicit cast operators allow the <b>FormattedMessage</b> to be used anywhere where strings are used.</remarks>
        public static implicit operator FormattedMessage(string str)
        {
            return new FormattedMessage(str);
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets the <b>IMessageFormatter</b> that formattes the values contained in the message.
        /// </summary>
        public IMessageFormatter Formatter
        {
            get { return this.formatter; }
            set { this.formatter = (value != null) ? value : new MessageFormatter(); }
        }

        /// <summary>
        /// Gets or sets the message text and format. The message format must obey the rules of the <see cref="System.String.Format(string, object)"/> method.
        /// </summary>
        public string MessageFormat
        {
            get { return this.messageFormat; }
            set { this.messageFormat = (value != null) ? value : ""; }
        }

        /// <summary>
        /// Gets or sets the list of values which are to be inserted into <b>MessageFormat</b> and formatted by <b>Formatter</b>.
        /// </summary>
        public ArrayList Values
        {
            get { return this.values; }
        }

        #endregion
    }
}